﻿using System;
using Microsoft.VisualStudio.Debugger.Interop;
using Red.Scripts.Debugger.Defines;

namespace Red.Scripts.Debugger.Engine.Breakpoint
{
    // This class represents a pending breakpoint which is an abstract representation of a breakpoint before it is bound.
    // When a user creates a new breakpoint, the pending breakpoint is created and is later bound. The bound breakpoints
    // become children of the pending breakpoint.
    internal class Pending : IDebugPendingBreakpoint2
    {
        #region Public Properties

        public IDebugBreakpointRequest2 Request { get; private set; }
        public IProgram DebuggedProgram { get; set; }
        public IDebugBoundBreakpoint2 Bound { get; private set; }
        public IDebugErrorBreakpoint2 Error { get; private set; }
        public enum_PENDING_BP_STATE State { get; private set; }

        public bool Virtual { get; private set; }

        #endregion Public Properties

        #region C-Tor

        public Pending( IDebugBreakpointRequest2 request )
        {
            Request = request;
        }

        #endregion C-Tor

        #region Event Handlers

        private void Breakpoint_BoundResult( object breakpoint )
        {
            Bound = breakpoint as IDebugBoundBreakpoint2;

            if ( Bound == null )
            {
                Error = breakpoint as IDebugErrorBreakpoint2;
            }
        }

        #endregion Event Handlers

        #region IDebugPendingBreakpoint2 Members

        // Binds this pending breakpoint to one or more code locations.
        int IDebugPendingBreakpoint2.Bind()
        {
            DebuggedProgram?.BindBreakpoint( this, State == enum_PENDING_BP_STATE.PBPS_ENABLED, Breakpoint_BoundResult );

            return Constants.S_OK;
        }

        // Determines whether this pending breakpoint can bind to a code location.
        int IDebugPendingBreakpoint2.CanBind( out IEnumDebugErrorBreakpoints2 ppErrorEnum )
        {
            ppErrorEnum = null;

            return Constants.S_OK;
        }

        // Deletes this pending breakpoint and all breakpoints bound from it.
        int IDebugPendingBreakpoint2.Delete()
        {
            State = enum_PENDING_BP_STATE.PBPS_DELETED;
            Bound?.Delete();

            Deleted?.Invoke( this, new EventArgs() );

            return Constants.S_OK;
        }

        // Toggles the enabled state of this pending breakpoint.
        int IDebugPendingBreakpoint2.Enable( int fEnable )
        {
            if ( State != enum_PENDING_BP_STATE.PBPS_DELETED )
            {
                State = fEnable != 0 ? enum_PENDING_BP_STATE.PBPS_ENABLED : enum_PENDING_BP_STATE.PBPS_DISABLED;

                Bound?.Enable( fEnable );
            }

            return Constants.S_OK;
        }

        // Enumerates all breakpoints bound from this pending breakpoint
        int IDebugPendingBreakpoint2.EnumBoundBreakpoints( out IEnumDebugBoundBreakpoints2 ppEnum )
        {
            IDebugBoundBreakpoint2[] breakpoints = Bound == null ? new IDebugBoundBreakpoint2[ 0 ] : new IDebugBoundBreakpoint2[] { Bound };
            ppEnum = new Defines.Enumerators.BoundBreakpoints( breakpoints );

            return Constants.S_OK;
        }

        // Enumerates all error breakpoints that resulted from this pending breakpoint.
        int IDebugPendingBreakpoint2.EnumErrorBreakpoints( enum_BP_ERROR_TYPE bpErrorType, out IEnumDebugErrorBreakpoints2 ppEnum )
        {
            if ( Error != null )
            {
                IDebugErrorBreakpointResolution2 resolution;
                int result = Error.GetBreakpointResolution( out resolution );

                if ( result != Constants.S_OK )
                {
                    ppEnum = null;
                    return result;
                }

                BP_ERROR_RESOLUTION_INFO[] info = new BP_ERROR_RESOLUTION_INFO[ 1 ];
                info[ 0 ] = new BP_ERROR_RESOLUTION_INFO();
                resolution.GetResolutionInfo( enum_BPERESI_FIELDS.BPERESI_TYPE, info );

                if ( result != Constants.S_OK )
                {
                    ppEnum = null;
                    return result;
                }

                if ( ( (int)bpErrorType & (int)enum_BP_ERROR_TYPE.BPET_TYPE_MASK & (int)info[ 0 ].dwType ) != 0 )
                {
                    ppEnum = new Defines.Enumerators.ErrorBreakpoints( new IDebugErrorBreakpoint2[] { Error } );
                    return Constants.S_OK;
                }
            }

            ppEnum = null;
            return Constants.S_FALSE;
        }

        // Gets the breakpoint request that was used to create this pending breakpoint
        int IDebugPendingBreakpoint2.GetBreakpointRequest( out IDebugBreakpointRequest2 ppBPRequest )
        {
            ppBPRequest = Request;

            return Constants.S_OK;
        }

        // Gets the state of this pending breakpoint.
        int IDebugPendingBreakpoint2.GetState( PENDING_BP_STATE_INFO[] pState )
        {
            pState[ 0 ].state = State;
            pState[ 0 ].Flags = Virtual? enum_PENDING_BP_STATE_FLAGS.PBPSF_VIRTUALIZED : enum_PENDING_BP_STATE_FLAGS.PBPSF_NONE;

            return Constants.S_OK;
        }

        // The sample engine does not support conditions on breakpoints.
        int IDebugPendingBreakpoint2.SetCondition( BP_CONDITION bpCondition )
        {
            return Constants.S_OK;
        }

        // The sample engine does not support pass counts on breakpoints.
        int IDebugPendingBreakpoint2.SetPassCount( BP_PASSCOUNT bpPassCount )
        {
            return Constants.S_OK;
        }

        // Toggles the virtualized state of this pending breakpoint. When a pending breakpoint is virtualized, 
        // the debug engine will attempt to bind it every time new code loads into the program.
        // The sample engine will does not support this.
        int IDebugPendingBreakpoint2.Virtualize( int fVirtualize )
        {
            Virtual = fVirtualize != 0;

            return Constants.S_OK;
        }

        #endregion

        #region Events

        public event EventHandler Deleted;

        #endregion Events
    }
}
